aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/(app)/[username]/messages
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2023-02-16 22:43:27 -0800
committerAdam Soltys <[email protected]>2023-02-16 22:43:27 -0800
commita97c7a00ebb414b7ff7731e6bf3640f2f476aae3 (patch)
tree4a067caf411a8fa6d206cdb978459d5d61a860d1 /src/routes/(app)/[username]/messages
parentc3968431bbdf534e2698256d8e8aa6f475275fff (diff)
downloadcoinos-ui-a97c7a00ebb414b7ff7731e6bf3640f2f476aae3.tar.gz
coinos-ui-a97c7a00ebb414b7ff7731e6bf3640f2f476aae3.zip
messages
Diffstat (limited to 'src/routes/(app)/[username]/messages')
-rw-r--r--src/routes/(app)/[username]/messages/+page.server.js19
-rw-r--r--src/routes/(app)/[username]/messages/+page.svelte126
2 files changed, 145 insertions, 0 deletions
diff --git a/src/routes/(app)/[username]/messages/+page.server.js b/src/routes/(app)/[username]/messages/+page.server.js
new file mode 100644
index 00000000..7b651389
--- /dev/null
+++ b/src/routes/(app)/[username]/messages/+page.server.js
@@ -0,0 +1,19 @@
+import { get } from '$lib/utils';
+
+export async function load({ params, parent, url }) {
+ let { user, subject } = await parent();
+ let { since = 0 } = params;
+
+ let messages = [];
+
+ try {
+ messages = await get(`/${user.pubkey}/${since}/messages`);
+ messages = messages
+ .filter((m) => m.recipient.id === subject.id || m.author.id === subject.id)
+ .sort((a, b) => a.created_at - b.created_at);
+ } catch (e) {
+ console.log(`failed to fetch nostr messages`, e);
+ }
+
+ return { messages, user };
+}
diff --git a/src/routes/(app)/[username]/messages/+page.svelte b/src/routes/(app)/[username]/messages/+page.svelte
new file mode 100644
index 00000000..f8f64aea
--- /dev/null
+++ b/src/routes/(app)/[username]/messages/+page.svelte
@@ -0,0 +1,126 @@
+<script>
+ import { browser } from '$app/environment';
+ import { t } from '$lib/translations';
+ import { format, parseISO } from 'date-fns';
+ import { scale } from 'svelte/transition';
+ import { enhance } from '$app/forms';
+ import { Avatar, Icon } from '$comp';
+ import { back, fail, focus } from '$lib/utils';
+ import { sign, send, encrypt, decrypt } from '$lib/nostr';
+ import { event as e, password } from '$lib/store';
+ import { tick, onMount } from 'svelte';
+ import { calculateId } from 'nostr';
+
+ export let data;
+
+ let { messages, subject, user } = data;
+ let input, pane, ready;
+
+ $: initialize($password);
+ let initialize = async (p) => {
+ if (!p) return;
+ ready = true;
+ tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+ };
+
+ e.subscribe((ev) => {
+ if (ev?.recipient.id === user.id && !~messages.findIndex((m) => m.id === ev.id)) {
+ messages.push(ev);
+ messages = messages;
+ tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+ }
+ });
+
+ let sent, submitting, message;
+ let submit = async () => {
+ submitting = true;
+
+ let event = {
+ pubkey: user.pubkey,
+ created_at: Math.floor(Date.now() / 1000),
+ kind: 4,
+ tags: [['p', subject.pubkey]]
+ };
+
+ event.message = message;
+ event.id = await calculateId(event);
+ tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+
+ try {
+ event.content = await encrypt({ message, recipient: subject.pubkey, user });
+ event.author = user;
+ event.recipient = subject;
+ await sign({ event, user });
+ await send(event);
+ messages.push(event);
+ messages = messages;
+ tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+
+ sent = true;
+ } catch (e) {
+ fail('Failed to send message');
+ }
+
+ message = '';
+ submitting = false;
+ };
+</script>
+
+<div class="container max-w-xl mx-auto px-4">
+ {#if messages.length && ready}
+ <div
+ class="max-h-[calc(40vh)] overflow-y-scroll scrollbar-thin scrollbar-thumb-[#F2F6FC] scrollbar-track-white pr-8"
+ bind:this={pane}
+ >
+ {#each messages as event}
+ {@const ours = event.pubkey === user.pubkey}
+ {@const theirs = !ours}
+ <div class="flex gap-2" class:flex-row-reverse={theirs} class:justify-end={theirs}>
+ <div
+ class="rounded-2xl px-4 py-2 max-w-[300px] mb-1 text-lg"
+ class:ours
+ class:theirs
+ :key={event.id}
+ >
+ {#await decrypt({ event, user })}
+ -
+ {:then message}
+ {message}
+ {/await}
+ </div>
+ <div class="mt-auto">
+ <Avatar user={event.author} size={'12'} />
+ </div>
+ </div>
+ <div class="text-sm text-gray-400 mb-6" class:text-right={ours}>
+ {format(new Date(event.created_at * 1000), 'MMM d')},
+ {format(new Date(event.created_at * 1000), 'h:mm aa')}
+ </div>
+ {/each}
+ </div>
+ {/if}
+
+ <form method="POST" class="space-y-5" on:submit|preventDefault={submit}>
+ <input type="hidden" name="requester_id" value={user.id} />
+ <input type="hidden" name="recipient" value={subject.username} />
+
+ <input
+ use:focus
+ name="message"
+ placeholder={$t('payments.sendMessage')}
+ class="w-full p-4 border rounded-xl outline-none"
+ bind:value={message}
+ bind:this={input}
+ />
+ </form>
+</div>
+
+<style>
+ .ours {
+ @apply bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] text-black rounded-br-none ml-auto;
+ }
+
+ .theirs {
+ @apply bg-gray-100 text-black rounded-bl-none;
+ }
+</style>